package apibasej.basic.misc.io;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.logging.Level;

import apibasej.basic.config.ConfigManager;
import apibasej.basic.exception.APIBaseException;

public class UtilConcCtrl extends ConfigManager implements Closeable{

	//chama este método no construtor, portanto basta instanciar sobrescrevendo este metodo
	//protected abstract void executeConcurrentSafe() throws Throwable;
	
	private FileLock lock = null;
	private File f = null;
	private String identOp = null;
	private FileOutputStream fos = null;
	
	public UtilConcCtrl(String identOp) throws Throwable {
		this(identOp,30000,false,100);
	}
	
	private static File tmpDir;
	public static File getTmpDir() {
		if(tmpDir==null){
			tmpDir = new File(System.getProperty("java.io.tmpdir"));
			tmpDir.mkdirs();
		}
		return tmpDir;
	}
	
	public UtilConcCtrl(String identOp, long timeMillisMaxWait, boolean throwExceptionIfTimeoutReached, long timeMillisWaitLoop) throws Throwable{
		this(identOp, timeMillisMaxWait, throwExceptionIfTimeoutReached, timeMillisWaitLoop, 0);
	}
	
	//timeMillisToWarnWait: limite de tempo de tolerancia para aleta
	public UtilConcCtrl(String identOp, long timeMillisMaxWait, boolean throwExceptionIfTimeoutReached, long timeMillisWaitLoop, long timeMillisToWarnWait) throws Throwable{
		
		this.identOp = identOp;
		
		boolean w = true;
		long remainingTimeWait = timeMillisMaxWait;
		
		f = new File(getTmpDir(),"cc_"+identOp+".tmp");
		fos = new FileOutputStream(f);
		
		while(w){
			
			try{
				
				if(lock!=null){//não deve ocorrer, pois se conseguir o lock irá terminar, e se não conseguir irá ficar null 
					w = false;//somente por garantia
					throw new APIBaseException(getClass().getSimpleName()+": lock!=null [identOp="+identOp+"]");
				}
				
				// o lock tenta esperar se já estiver ocupado, o tryLock tenta na hora e lança exceção se não conseguir ???
				// como terá que esperar que qualquer jeito, é melhor usar o lock ???
				lock = fos.getChannel().lock();
				
				//**********************************
				//irá lockar e continuar as próximas execuções depois do construtor
				w = false;//somente por garantia
				return;
				//**********************************

			}catch (OverlappingFileLockException oe) {
				
				if(remainingTimeWait>0){
					try {
						remainingTimeWait -= timeMillisWaitLoop;
						
						//só registra alerta se esperar mais que o limite de tempo de tolerancia para aleta
						if(timeMillisMaxWait-remainingTimeWait>timeMillisToWarnWait){
							String m = getClass().getName()+": [identOp="+identOp+"] Waiting ... (remainingTimeWait="+remainingTimeWait+", maxWait="+timeMillisMaxWait+", waitLoop="+timeMillisWaitLoop+")";
							logDef(Level.WARNING, m, oe);
							//System.out.println(m);
						}
						
						Thread.sleep(timeMillisWaitLoop);
						//depois pode-se tirar este warn ... deixar por enquanto somente para verificar se tem ações concorrentes ocorrendo
						continue;
						
					} catch (InterruptedException ie) {
						logDef(Level.WARNING, "Erro sleep "+getClass().getName()+" [identOp="+identOp+"]: "+ie.getMessage(),ie);
					}
				}else{
					ConcurrentCtrlException aex = new ConcurrentCtrlException(getClass().getSimpleName()+" [identOp="+identOp+"] TIMEOUT REACHED: "+timeMillisMaxWait+", identOp="+identOp,oe);
					if(throwExceptionIfTimeoutReached){
						throw aex;
					}else{
						delOnExit(true);
						logDef(Level.SEVERE, aex.getMessage(), aex);
						
						//**********************************
						//irá continuar as próximas execuções depois do construtor
						w = false;//somente por garantia
						return;
						//**********************************
					}
				}
			}
		}
	}
	
	public String getIdentOp() {
		return identOp;
	}
	
	private void delOnExit(boolean delNow){
		if(f!=null){
			try {
				f.deleteOnExit(); // para apagar quando liberar o lock ???
				if(delNow){
					f.delete();
				}
			} catch (Throwable e) {
				logDef(Level.SEVERE, getClass().getName()+": f.deleteOnExit(): "+e.getMessage(),e);	
			}
		}
	}

	//irá liberar o lock ...
	@Override
	public void close() throws IOException {
		
		delOnExit(true);
		
		if(lock!=null){
			//w = false;
			try {
				lock.release();
			} catch (Throwable e) {
				logDef(Level.SEVERE, getClass().getName()+" [identOp="+identOp+"]: Erro lock.release(): "+e.getMessage(),e);	
				return;
			}
		}
		
		
		if(fos!=null){
			try {
				fos.close();
			} catch (Throwable e) {
				logDef(Level.SEVERE, getClass().getName()+" [identOp="+identOp+"]: Erro fos.close(): "+e.getMessage(),e);	
			}
		}
		
		/* não tentar aqui pois
		//Pode tentar apagar depois de outra thread pegar o processo?
		if(f!=null){
			try {
				f.delete(); // somente por garantia, pois o deleteOnExit() deve apagar quando liberar o lock
				f.deleteOnExit(); // para apagar quando liberar o lock ???
			} catch (Throwable e) {
				logSysOut(Level.SEVERE, getClass().getName()+": f.delete(): "+e.getMessage(),e);	
			}
		}
		*/
		
	}
	
	
}
